<?php

class Pictures_PicturesModel extends BlogPicturesBaseModel
{
    /**
     * Path to picture files storage
     *
     * @var string
     */
    private $storage = null;
    
    /**
     * List of supported image types
     *
     * @var array
     */
    protected static $supported_types = array(IMAGETYPE_PNG, IMAGETYPE_JPEG);
    
    /**
     * List of supported preview sizes
     *
     * @var array
     */
    protected static $preview_sizes = array('medium', 'small');
    
    /**
     * Create new picture file using file uploaded from the browser.
     *
     * @param AgaviUploadedFile $file
     * @param string $picture_name
     * @return Picture Newly added Picture instance or null
     * @throws Doctrine_Exception, PictureDuplicateException, PictureNotSupportedFormatException,
     * 		WereWordFilesystemException, WereWordException
     */
    public function newPicture(AgaviUploadedFile $file, $picture_name)
    {
        $res = Doctrine::getTable('Picture')->findByName($picture_name);

        if ($res->count() != 0) {
            throw new PictureDuplicateException();
        }
        
        list($suffix, $content_type) = $this->processUploadedPicture($file);
        
        $con = $this->getPdo();
        $con->beginTransaction();
        $picture = null;
        
        try {
           /* @var $picture Picture */
           $picture = new Picture();
           $picture->name = $picture_name;
           $picture->filename = $suffix;
           $picture->content_type = $content_type;
           $picture->save($con);
           $con->commit();
           
        } catch (Doctrine_Exception $e) {
            $con->rollback();
            throw $e;
        }
        
        return $picture;
    }
    
    /**
     * Delete picture with given id
     *
     * @param int $picture_id
     * @throws Doctrine_Exception
     */
    public function deletePicture($picture_id)
    {
        $picture = $this->getPicture($picture_id);
        $con = $this->getPdo();
        
        $con->beginTransaction();
        try {
            $picture->delete($con);
            $con->commit();
        } catch (Doctrine_Exception $e) {
            $con->rollback();
            throw $e;
        }
    }
    
    /**
     * Retrieve Picture instance
     *
     * @param int $id DB Id of the picture, may be null, in that case $picture_name will be used
     * @param string $picture_name name of the picture
     * @return Picture Or null if picture not found
     */
    public function getPicture($picture_id, $picture_name=null)
    {
        $picture = null;
        if (!is_null($picture_id)) {
            $picture = Doctrine::getTable('Picture')->find($picture_id);
        } else {
            $picture = Doctrine::getTable('Picture')->findByName($picture_name)->getFirst();
        }
        
        return $picture;
    }
    
    /**
     * Retrieve image file content and correspondig MIME type
     *
     * @param Picture $picture
     * @param string $size Expected image size, must be one of supported preview sizes
     * @return array array($content_type, $data, $last_modified)
     * @throws WereWordException
     */
    public function getPictureData(Picture $picture, $size='')
    {
        $base_path = $this->getStorage() . DIRECTORY_SEPARATOR . 'image' . $picture->filename;
        $content_type = $picture->content_type; 
        if (!is_null($size) && in_array($size, self::$preview_sizes)
            && file_exists($base_path . $size)) 
        {
            $base_path .= $size;
            # preview are in JPEG format
            $content_type = 'image/jpeg';
        }
        if (!is_file($base_path)) {
            throw new WereWordException('File not found');
        }
        $data = file_get_contents($base_path);
        $mtime = filemtime($base_path);
        
        return array($content_type, $data, $mtime);
    }
    
    /**
     * Retrieve file names of picture data.
     * 
     * @param Picture $picture
     * @return array Associative array of filenames from the storage, array key is the one of 
     *      'original', 'small', 'medium'
     */
    public function getPictureDataFiles(Picture $picture)
    {
        $base_path = $this->getStorage() . DIRECTORY_SEPARATOR . 'image' . $picture->filename;
        $result = array();
        
        if (is_file($base_path)) {
            $result['original'] = $base_path;
        }
        foreach (self::$preview_sizes as $size) {
            if (is_file($base_path.$size)) {
                $result[$size] = $base_path.$size;
            }
        }
        return $result;
    }
    
    
    /**
     * Retrieve list of all pictures.
     * 
     * @return array
     */
    public function findPictures()
    {
        $query = Doctrine_Query::create()
            ->from('Picture p')
            ->orderBy('p.id')
            ;
        return $query->execute();
    }
    
    /**
     * Update picture, set another image file.
     *
     * @param Picture $picture
     * @param mixed $new_file AgaviUploadedFile instance or null
     * @throws WereWordFilesystemException, PictureNotSupportedFormatException, Doctrine_Exception
     */
    public function updatePicture(Picture $picture, $new_file, $new_name=null)
    {
        if (is_null($new_file) && is_null($new_name)) {
            // nothing to do
            return;
        }
        
        $new_suffix = null;
        if ($new_file instanceof AgaviUploadedFile) {
            list($new_suffix, $content_type) = $this->processUploadedPicture($new_file);
        }
        
        $con = $this->getPdo();
        $con->beginTransaction();
        $old_suffix = $picture->filename;
        
        try {
            if (!is_null($new_suffix)) {
                $picture->filename = $new_suffix;
                $picture->content_type = $content_type;
            }
            if (!is_null($new_name)) {
                $picture->name = $new_name;
            }
            $picture->save($con);
            $con->commit();
            
            if (!is_null($new_path)) {
                // delete old image files
                $base = $this->getStorage() . DIRECTORY_SEPARATOR . 'image' . $old_suffix;
                // Just ignore errors, we cannot do anything in case of error
                @unlink($base);
                foreach (self::$preview_sizes as $size) {
                    @unlink($base . $size);
                }
            }
        } catch (Doctrine_Exception $e) {
            $con->rollback();
            throw $e;
        }
    }
    
    /**
     * Return path to image files storage.
     *
     * @return string
     */
    protected function getStorage()
    {
        if (is_null($this->storage)) {
            $this->storage = AgaviConfig::get('wereword.pictures_store_dir');
        }
        return $this->storage;
    }
    
    /**
     * Validate image file and return its type
     *
     * @param string $filename
     * @return string
     * @throws PictureNotSupportedFormatException
     */
    protected static function validateImageFile($filename)
    {
        $image_type = exif_imagetype($filename);
        if ($image_type === FALSE || !in_array($image_type, self::$supported_types)) {
            throw new PictureNotSupportedFormatException();
        }
        return $image_type;
    }
    
    /**
     * Create image GD object from file
     *
     * @param string $filename
     * @param integer $image_type	null or one of IMAGETYPE_* constants 
     */
    protected static function createImageFromFile($filename, $image_type=null)
    {
        if (is_null($image_type)) {
            $image_type = self::validateImageFile($filename);
        }
        
        $image = FALSE;
        
        switch ($image_type) {
        case IMAGETYPE_PNG:
            $image = @imagecreatefrompng($filename);
            break;
            
        case IMAGETYPE_JPEG:
            $image = @imagecreatefromjpeg($filename);
            break;
        }
        
        if ($image === FALSE) {
            throw new WereWordException('Unable to create image from file');
        }
        
        return $image;
    }
    
    /**
     * Put image file into the storage
     *
     * @param string $src_filename Source file with picture
     * @param string $target_filename Target file name without a path component
     * @throws WereWordFilesystemException
     */
    public function putFileToStorage($src_filename, $target_filename)
    {
        if (!is_file($src_filename)) {
            throw new WereWordFilesystemException('Unable to put file to storage: source file not found.');
        }
        
        if (copy($src_filename, $this->getStorage().DIRECTORY_SEPARATOR.$target_filename) === FALSE) {
            throw new WereWordFilesystemException('Unable to put file to storage, see server log files for details.');
        }
    }
    
    /**
     * Process uploaded picture
     *
     * @param AgaviUploadedFile $file
     * @return array Return suffix of just added group of files and MIME type of image
     */
    protected function processUploadedPicture(AgaviUploadedFile $file)
    {
        $src_filename = $file->tmp_name;
        $image_type = self::validateImageFile($src_filename);
        $content_type = image_type_to_mime_type($image_type);
        
        // copy image file into storage
        // TODO: use method makeRandomFile instead
        $target_path = tempnam($this->getStorage(), 'image');
        $target_dirname = dirname($target_path);
        $target_file = basename($target_path);
        // make sure that file was created in the right place
        if ($target_dirname != $this->getStorage()) {
            throw new WereWordFilesystemException('Access to the storage failed');
        }
        
        // get file suffix
        $suffix = substr($target_file, strlen('image'));
        $target_file_base = $target_path;
            
        // try to load image
        $image = self::createImageFromFile($src_filename, $image_type);
        $image_width = imagesx($image);
        $image_height = imagesy($image);
        
        // try to load image
        $image = self::createImageFromFile($src_filename);
        // get image dimensions
        $image_width = imagesx($image);
        $image_height = imagesy($image);
        
        // list with files we are going to create
        $files = array();
        try {
            // first put original file
            if (@copy($src_filename, $target_file_base) === FALSE) {
                throw new WereWordFilesystemException('Unable to copy source file to the storage.');
            }
            $files[] = $target_file_base;
            
            // now generate previews if required
            foreach (self::$preview_sizes as $size) {
                list($w, $h) = WereWordConfig::mget(array('blog.pictures.'.$size.'_preview_width', 
                    'blog.pictures.'.$size.'_preview_height'));
                if ($image_width > $w || $image_height > $h) {
                    // calculate size of destintaion image
                    $k = max(array($image_width / $w, $image_height / $h));
                    $new_w = floor($image_width/$k);
                    $new_h = floor($image_height/$k);
                    $preview = imagecreatetruecolor($new_w, $new_h);
                    imagecopyresized($preview, $image, 0, 0, 0, 0, $new_w, $new_h, $image_width, $image_height);
    
                    $target_path = $target_file_base . $size;
                    if (imagejpeg($preview, $target_path) === FALSE) {
                        throw new WereWordFilesystemException('Unable to write size preview: ' . $size);
                    }
                    $files[] = $target_path;
                }
            }
            
        } catch (WereWordFilesystemException $e) {
            // remove all created files
            foreach ($files as $filename) {
                @unlink($file);
            }
            throw $e;
        }
        
        return array($suffix, $content_type);
    }
}

?>
